﻿#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)

#include <stdio.h>

/* 1.指针概念*/

//内存是电脑上特别重要的存储器，为了有效地使用内存就把内存划分成一个个的内存单元(1个字节)
//为了有效的访问到内存的每个单元就给每个内存单元进行编号，编号就称为内存单元的地址,也叫做指针
//内存单元的地址都是由硬件自动生成,那具体怎么产生？
//	答:对于32位(x86)的机器，假设有32根地址线，那么假设每根地址线在寻址的时候产生高电平或低电平就是1或0,就有2的32次方个地址
//     若1个地址管理1个内存单元,那我们就可以给(2^32Byte==2^32/1024KB ==2^32/1024/1024MB==2^32/1024/1024/1024GB==4GB)4G的空间进行编址。

//指针理解的2个要点：
//1. 内存单元的编号，就是地址，也就是指针
//2. 平时口语中说的指针，通常指的是指针变量，用来存放地址或指针的变量

//总结：
//指针变量是用来存放地址的，地址是唯一标示一个内存单元的。
//指针变量的大小在32位平台(x86)是4个字节，在64位平台(x64)是8个字节。

//int main()
//{
//	int a = 10;
//	int* p = &a;		//p是指针变量,用来存放地址，也就是存指针
//
//	return 0;
//}


/* 2.指针类型*/

//x86是32位环境，x64是64位环境
//char* 类型的指针变量是为了存放 char 类型变量的地址。
//short* 类型的指针变量是为了存放 short 类型变量的地址。
//int* 类型的指针变量是为了存放 int 类型变量的地址

//int main()
//{
//	char* pc = NULL;
//	short* ps = NULL;
//	int* pi = NULL;
//	double* pd = NULL;
//	//sizeof返回值的类型是无符号整型 unsigned int,用%zu打印最合适
//	printf("%zu\n", sizeof(pc));		
//	printf("%zu\n", sizeof(ps));
//	printf("%zu\n", sizeof(pi));
//	printf("%zu\n", sizeof(pd));
//
//	return 0;
//}

//2.1指针的解引用

//int main()
//{
//	int a = 0x11223344;		//1个16进制数用4个二进制位表示,2个16进制数就代表1个字节,8个16进制数正好占用4个字节大小的空间
//	//int* pa = &a;
//	//*pa = 0;		//解引用时访问4个字节
//
//	char* pc = (char*)&a;
//	*pc = 0;		//解引用时只访问1个字节
//
//	return 0;
//}

//指针类型决定了指针变量在被解引用时访问了几个字节
//char* 的指针变量被解引用时就只访问一个字节，而 int* 的指针变量被解引用时能访问四个字节

//2.2指针+-整数

//int main()
//{
//	int a = 0x11223344;	
//	int* pa = &a;
//	char* pc = (char*)&a;
//
//	printf("%p\n", pa);			
//	printf("%p\n", pa+1);		//跳过4个字节
//
//	printf("%p\n", pc);
//	printf("%p\n", pc+1);		//跳过1个字节
//
//	return 0;
//}

//指针变量的类型决定了指针变量+-1操作时跳过几个字节

/* 3. 野指针*/
//野指针就是指针指向的位置是不可知的（随机的、不正确的、没有明确限制的）

//3.1 野指针成因
//1. 指针未初始化
//int main()
//{
//	int* p;		//局部变量指针未初始化，默认为随机值
//	*p = 10;	//非法访问内存
//
//	return 0;
//}

//2. 指针越界访问
//int main()
//{
//	int arr[10] = { 0 };
//	int* p = arr;
//	int i = 0;
//	for (i = 0; i <= 10; i++)
//	{
//		*p = i;
//		p++;		//当指针指向的范围超出数组arr的范围时，p就是野指针
//	}
//	return 0;
//}

//3. 指针指向的空间释放


//3.2 如何规避野指针
//1. 指针初始化
//2. 小心指针越界
//3. 指针指向空间释放，及时置NULL;	NULL是零地址,对于零地址是不能访问的
//4. 避免返回局部变量的地址!
//5. 指针使用之前检查有效性

//int main()
//{
//	int* p = NULL;	//null是零地址, 对于零地址是不能访问的
//	//*p = 100;		//对于零地址是不能访问的，这种写法错误！
//
//	int a = 10;
//	p = &a;
//
//	if (p != NULL)
//	{
//		*p = 20;
//	}
//	return 0;
//}


/* 4. 指针运算*/
//4.1 指针 + -整数

//#define N_VALUES 5
//float values[N_VALUES];		//定义的数组有5个元素，但没有初始化
//float* vp;		//vp就是野指针
//指针+-整数；指针的关系运算
//for (vp = &values[0]; vp < &values[N_VALUES];)		//vp < &values[N_VALUES]就是指针的关系运算
//{
//	*vp++ = 0;		//后置++，先执行*vp=0,再vp++
//}

//int main()
//{
//	int arr[10] = { 0 };
//	int i = 0 ;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	//for (i = 0; i < sz; i++)
//	//{
//	//	arr[i] = 1;		//把这10个元素全部初始化为1
//	//}
//
//	//int* p = arr;
//	//for (i = 0; i < sz; i++)
//	//{
//	//	*p = 1;		//把这10个元素全部初始化为1
//	//	p++;
//	//}
//
//	int* p = arr;
//	for (i = 0; i < sz; i++)
//	{
//		*(p+i)=1;		//p+i可理解为下标为i的地址
//	}
//
//	return 0;
//}

//4.2 指针 - 指针

//int main()
//{
//	int arr[10] = { 0 };
//	printf("%d\n", &arr[9] - &arr[0]);		//9
//
//	return 0;
//}
// |指针-指针|得到的是指针和指针之间元素的个数
// 不是所有指针都能相减，指向同一块空间的2个指针才能相减

#include <string.h>

/* 计算字符串长度的3种方法*/
//1.普通方法
//int my_strlen(char* str)		//str就是指针变量，指向a的地址
//{
//	int count = 0;
//	while (*str != '\0')
//	{
//		count++;
//		str++;
//	}
//
//	return count;
//}

//2.递归

//3.利用指针-指针：用'\0'的地址减去起始地址即可
//int my_strlen(char* str)	//str就是指针变量，指向a的地址
//{
//	char* start = str;		//指针变量start就是起始地址
//	while (*str != '\0')
//	{
//		str++;
//	}						//跳出循环时，指针变量str就是指向'\0'的地址
//
//	return (str - start);
//}

//int main()
//{
//	int len = my_strlen("abcdef");		//字符串传参时实际上传的是a的地址，不是abcdef本身
//	printf("%d\n", len);
//
//	return 0;
//}


//4.3 指针的关系运算

//#define N_VALUES 5
//float values[N_VALUES];		//定义的数组有5个元素，但没有初始化
//float* vp;		//vp就是野指针
//for (vp = &values[0]; vp < &values[N_VALUES];)		//vp < &values[N_VALUES]就是指针的关系运算
//{
//	*vp++ = 0;		//后置++，先执行*vp=0,再vp++
//}
//
//for (vp = &values[N_VALUES]; vp > &values[0];)
//{
//	*--vp = 0;
//}
//					||简化
//for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
//{
//	*vp = 0;
//}
//简化后的代码实际上在绝大部分的编译器上是可行的，但应避免这样写，因为标准并不保证它可行。
//标准规定：允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较，但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

/* 5.指针和数组*/

//int main()
//{
//	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
//	int* p = arr; //指针存放数组首元素的地址
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
//	}
//	return 0;
//}
//p+i 其实计算的是数组arr下标为i的地址。
//arr[i]本质上就是*(arr+i)


/*6. 二级指针*/
//指针变量也是变量，是变量就有地址，那指针变量的地址存放在哪里？
//int main()
//{
//	int a = 10;
//	int* pa = &a;		//pa是一个一级指针变量
//	*pa = 20;
//	printf("%d\n", a);
//
//	int** ppa = &pa;	//ppa是一个二级指针变量;后面的*代表ppa是指针，前面的int*说明ppa指向的对象pa的类型是int*
//	int b = 20;
//	*ppa = &b;		//等价于 pa = &b;*ppa 通过对ppa中的地址进行解引用，这样找到的是pa，*ppa其实访问的就是pa
//
//	**ppa = 30;		//**ppa先通过*ppa找到pa，然后对pa进行解引用操作：*pa ，那找到的是a
//	//等价于*pa = 30;
//	//等价于a = 30;
//	return 0;
//}


/*7. 指针数组*/
//指针数组是指针还是数组？
//答案：是数组。是存放指针的数组。
//数组我们已经知道整形数组，字符数组。那指针数组是怎样的？

//int main()
//{
//	int a = 10;
//	int b = 20;
//	int c = 30;
//
//	//int arr[10];
//
//	int* pa = &a;
//	int* pb = &b;
//	int* pc = &c;
//
//	//parr就是存放指针的数组-指针数组
//	int* parr[10] = {&a, &b, &c};
//
//	int i = 0;
//	for (i = 0; i < 3; i++)//0 1 2
//	{
//		printf("%d ", *(parr[i]));	//parr[0]就是&a,在解引用就是a
//	}
//
//	return 0;
//}

//可用指针数组模拟出二维数组
//int main()
//{
//	//int arr[3][4] = { 1,2,3,4, 2,3,4,5, 3,4,5,6 };
//	////1 2 3 4
//	////2 3 4 5
//	////3 4 5 6
//	//int i = 0;
//	//int j = 0;
//	//for (i = 0; i < 3; i++)
//	//{
//	//	for (j = 0; j < 4; j++)
//	//	{
//	//		printf("%d ", arr[i][j]);
//	//	}
//	//	printf("\n");
//	//}
//
//	int arr1[4] = { 1,2,3,4 };
//	int arr2[4] = { 2,3,4,5 };
//	int arr3[4] = { 3,4,5,6 };
//
//	int* parr[3] = { arr1, arr2, arr3 };
//	int i = 0;
//	for (i = 0; i < 3; i++)
//	{
//		int j = 0;
//		for (j = 0; j < 4; j++)
//		{
//			printf("%d ", parr[i][j]);
//		}
//		printf("\n");
//	}
//
//	return 0;
//}